﻿using System;
using System.Windows.Forms;

namespace PhenomMsrTweaker
{
	/// <summary>
	/// Represents a set of P-state settings for all cores of a CPU.
	/// </summary>
	public sealed partial class PStateControl : UserControl
	{
		private static readonly int _numCores = System.Environment.ProcessorCount;

		// cached:
		private static double _maxMulti = -1;
		private static double _minVid, _maxVid;

		private int _index = -1; // 0-4
		private PState _pState;

		private int _optimalWidth;
		private bool _modified;


		/// <summary>
		/// Gets or sets the associated hardware P-state index (0-4).
		/// </summary>
		public int PStateIndex
		{
			get { return _index; }
			set
			{
				if (value < 0 || value > 4)
					throw new ArgumentOutOfRangeException("PStateIndex");

				_index = value;
			}
		}

		/// <summary>
		/// Gets a value indicating whether the control has been modified by the user
		/// since the last load/save operation.
		/// </summary>
		public bool IsModified
		{
			get { return _modified; }
		}

		/// <summary>
		/// Gets the currently selected CPU VID.
		/// </summary>
		public double CpuVid
		{
			get { return (double)cpuVidNumericUpDown.Value; }
		}

		/// <summary>
		/// Gets the currently selected NB VID.
		/// </summary>
		public double NbVid
		{
			get { return (double)nbVidNumericUpDown.Value; }
		}


		/// <summary>
		/// Constructor.
		/// </summary>
		public PStateControl()
		{
			InitializeComponent();

			// check if the CPU's maximum multi is limited (non Black Edition CPUs)
			if (_maxMulti < 0)
			{
				// in DesignMode, Program.Ols is null
				if (Program.Ols == null)
				{
					_maxMulti = 31.5;
					_minVid = 0.0125;
					_maxVid = 1.55;
				}
				else
				{
					_maxMulti = K10Manager.GetMaxMulti();
					K10Manager.GetVidLimits(out _minVid, out _maxVid);
				}
			}

			cpuVidNumericUpDown.Minimum = nbVidNumericUpDown.Minimum = (decimal)_minVid;
			cpuVidNumericUpDown.Maximum = nbVidNumericUpDown.Maximum = (decimal)_maxVid;

			// add as many NumericUpDown controls as there are CPU cores for the multis
			for (int i = 0; i < _numCores; i++)
			{
				var control = new NumericUpDown()
				{
					AutoSize = true,
					DecimalPlaces = 1,
					Increment = (decimal)0.5,
					Maximum = (decimal)_maxMulti,
					Minimum = 4,
					TabIndex = i,
					TextAlign = HorizontalAlignment.Center,
					Value = 4,
				};
				toolTip1.SetToolTip(control, "Multiplier for core " + (i+1) + ".\r\nx times the reference clock (default: 200 MHz) yields the core speed.");

				control.ValueChanged += (s, e) => _modified = true;

				if (i == 0)
				{
					control.ValueChanged += (s, e) =>
					{
						for (int j = 1; j < _numCores; j++)
						{
							var otherControl = (NumericUpDown)flowLayoutPanel1.Controls[j];
							otherControl.Value = control.Value;
						}
					};
				}

				flowLayoutPanel1.Controls.Add(control);
			}

			cpuVidNumericUpDown.ValueChanged += (s, e) => _modified = true;
			 nbVidNumericUpDown.ValueChanged += (s, e) => _modified = true;

			// set the tab order
			cpuVidNumericUpDown.TabIndex = 3 + _numCores;
			nbVidNumericUpDown.TabIndex = cpuVidNumericUpDown.TabIndex + 1;
			refreshButton.TabIndex = nbVidNumericUpDown.TabIndex + 1;

			// compute the optimal width, based on the number of cores
			_optimalWidth = label1.Width + label1.Margin.Horizontal + flowLayoutPanel1.Controls.Count *
				(flowLayoutPanel1.Controls[0].Width + flowLayoutPanel1.Controls[0].Margin.Horizontal) + 100;

			refreshButton.Click += (s, e) => LoadFromHardware();
		}

		/// <summary>
		/// Returns the delta for the optimal width, based upon the number of cores.
		/// </summary>
		public int GetDeltaOptimalWidth()
		{
			return (_optimalWidth - this.Width);
		}


		/// <summary>
		/// Loads the P-state settings from each core's MSR.
		/// </summary>
		public void LoadFromHardware()
		{
			if (_index < 0)
				throw new InvalidOperationException("The PStateIndex property needs to be initialized first.");

			_pState = PState.Load(_index);

			double maxCpuVid = 0, maxNbVid = 0;
			for (int i = 0; i < _pState.Msrs.Length; i++)
			{
				var msr = _pState.Msrs[i];

				var control = (NumericUpDown)flowLayoutPanel1.Controls[i];
				control.Value = (decimal)msr.Multi;

				maxCpuVid = Math.Max(maxCpuVid, msr.CpuVid);
				maxNbVid = Math.Max(maxNbVid, msr.NbVid);
			}

			cpuVidNumericUpDown.Value = Math.Min(cpuVidNumericUpDown.Maximum, (decimal)maxCpuVid);
			nbVidNumericUpDown.Value = Math.Min(nbVidNumericUpDown.Maximum, (decimal)maxNbVid);

			_modified = false;
		}

		/// <summary>
		/// Saves the current P-state settings to each core's MSR.
		/// </summary>
		public void Save()
		{
			if (!_modified)
				return;

			if (_pState == null)
				throw new InvalidOperationException("Load a P-state first for safe initialization.");

			for (int i = 0; i < _numCores; i++)
			{
				var control = (NumericUpDown)flowLayoutPanel1.Controls[i];

				_pState.Msrs[i].Multi = (double)control.Value;
				_pState.Msrs[i].CpuVid = (double)cpuVidNumericUpDown.Value;
				_pState.Msrs[i].NbVid = (double)nbVidNumericUpDown.Value;
			}

			_pState.Save(_index);

			_modified = false;
		}
	}
}
